package spark;

import java.util.Stack;
import java.util.Collection;
import java.util.LinkedList;

import spark.identity.IManageable;
import spark.identity.IIdentifiable;
import spark.exception.ObjectNotFoundException;
import spark.exception.ObjectAlreadyContainedException;

/**
 * ObjectManager handles objects implementing IManagable. It makes sure each object passed to it is only loaded
 * once to improve memory usage.
 */
public class ObjectManager {
	private static ObjectManager instance = new ObjectManager();

	private Collection<Stack<IManageable>> elements = new LinkedList<Stack<IManageable>>();

	private ObjectManager() {
	}

	/**
	 * Retrieves an object by looking for the provided id. The object must've been inserted by {@link #insertObject(spark.identity.IManageable)} insertObject}
	 * @param id    Id of the object stored in the ObjectManager to reference against.
	 * @param <ObjectType>  Generic parameter used to identify the type of the object to retrieve.
	 *                      ObjectManager performs a cast against this type before returning the result.
	 * @return      object matching the provided {@code id} castetd against the specified {@code ObjectType}.
	 * @throws ObjectNotFoundException  if the {@code id} could not be matched against any stored object.
	 */
	@SuppressWarnings({"unchecked"})
	public <ObjectType extends IManageable> ObjectType getObjectById(IIdentifiable id) throws ObjectNotFoundException {
		for(Stack<IManageable> currentElement : elements) {
			//on id match push a new reference on the stack and return the corresponding object
			if(currentElement.firstElement().getId().equals(id)) {
				currentElement.push(currentElement.firstElement());
				return (ObjectType) currentElement.firstElement();
			}
		}
		throw new ObjectNotFoundException();
	}

	/**
	 * Releases an object previously obtained by {@link #getObjectById(spark.identity.IIdentifiable)} getObjectById}.
	 * @param object    Object previously obtained by this method.
	 * @throws ObjectNotFoundException  if the object could not be found in the manager.
	 */
	public void releaseObject(IManageable object) throws ObjectNotFoundException {
		for(Stack<IManageable> currentElement : elements) {
			//on id match pop a reference from the stack
			if(currentElement.firstElement().getId().equals(object.getId())) {
				currentElement.pop();
				//remove stack and dispose of the object once it is no longer needed
				if(currentElement.isEmpty()) {
					elements.remove(currentElement);
					object.dispose();
				}
				return;
			}
		}
		throw new ObjectNotFoundException();
	}

	/**
	 * Inserts a new object into the manager.
	 * @param object    Object to insert.
	 * @throws ObjectAlreadyContainedException  if the object is already contained.
	 */
	public void insertObject(IManageable object) throws ObjectAlreadyContainedException {
		if(isObjectContained(object.getId())) {
			throw new ObjectAlreadyContainedException();
		}
		Stack<IManageable> newStack = new Stack<IManageable>();
		newStack.push(object);
		elements.add(newStack);
	}

	/**
	 * Checks if a given id is already contained within the manager.
	 * @param id    Id to check for.
	 * @return  true if the id is contained, false otherwise.
	 */
	public boolean isObjectContained(IIdentifiable id) {
		for(Stack<IManageable> currentElement : elements) {
			if(currentElement.firstElement().getId().equals(id)) {
				return true;
			}
		}
		return false;
	}

	public static ObjectManager getInstance() {
		return instance;
	}
}
